{
  "cells": [
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "# code by Tae Hwan Jung @graykode\n",
        "import argparse\n",
        "import numpy as np\n",
        "import torch\n",
        "import torch.nn as nn\n",
        "\n",
        "# S: Symbol that shows starting of decoding input\n",
        "# E: Symbol that shows starting of decoding output\n",
        "# P: Symbol that will fill in blank sequence if current batch data size is short than time steps\n",
        "\n",
        "def make_batch():\n",
        "    input_batch, output_batch, target_batch = [], [], []\n",
        "\n",
        "    for seq in seq_data:\n",
        "        for i in range(2):\n",
        "            seq[i] = seq[i] + 'P' * (n_step - len(seq[i]))\n",
        "\n",
        "        input = [num_dic[n] for n in seq[0]]\n",
        "        output = [num_dic[n] for n in ('S' + seq[1])]\n",
        "        target = [num_dic[n] for n in (seq[1] + 'E')]\n",
        "\n",
        "        input_batch.append(np.eye(n_class)[input])\n",
        "        output_batch.append(np.eye(n_class)[output])\n",
        "        target_batch.append(target) # not one-hot\n",
        "\n",
        "    # make tensor\n",
        "    return torch.FloatTensor(input_batch), torch.FloatTensor(output_batch), torch.LongTensor(target_batch)\n",
        "\n",
        "# Model\n",
        "class Seq2Seq(nn.Module):\n",
        "    def __init__(self):\n",
        "        super(Seq2Seq, self).__init__()\n",
        "\n",
        "        self.enc_cell = nn.RNN(input_size=n_class, hidden_size=n_hidden, dropout=0.5)\n",
        "        self.dec_cell = nn.RNN(input_size=n_class, hidden_size=n_hidden, dropout=0.5)\n",
        "        self.fc = nn.Linear(n_hidden, n_class)\n",
        "\n",
        "    def forward(self, enc_input, enc_hidden, dec_input):\n",
        "        enc_input = enc_input.transpose(0, 1) # enc_input: [max_len(=n_step, time step), batch_size, n_class]\n",
        "        dec_input = dec_input.transpose(0, 1) # dec_input: [max_len(=n_step, time step), batch_size, n_class]\n",
        "\n",
        "        # enc_states : [num_layers(=1) * num_directions(=1), batch_size, n_hidden]\n",
        "        _, enc_states = self.enc_cell(enc_input, enc_hidden)\n",
        "        # outputs : [max_len+1(=6), batch_size, num_directions(=1) * n_hidden(=128)]\n",
        "        outputs, _ = self.dec_cell(dec_input, enc_states)\n",
        "\n",
        "        model = self.fc(outputs) # model : [max_len+1(=6), batch_size, n_class]\n",
        "        return model\n",
        "\n",
        "if __name__ == '__main__':\n",
        "    n_step = 5\n",
        "    n_hidden = 128\n",
        "\n",
        "    char_arr = [c for c in 'SEPabcdefghijklmnopqrstuvwxyz']\n",
        "    num_dic = {n: i for i, n in enumerate(char_arr)}\n",
        "    seq_data = [['man', 'women'], ['black', 'white'], ['king', 'queen'], ['girl', 'boy'], ['up', 'down'], ['high', 'low']]\n",
        "\n",
        "    n_class = len(num_dic)\n",
        "    batch_size = len(seq_data)\n",
        "\n",
        "    model = Seq2Seq()\n",
        "\n",
        "    criterion = nn.CrossEntropyLoss()\n",
        "    optimizer = torch.optim.Adam(model.parameters(), lr=0.001)\n",
        "\n",
        "    input_batch, output_batch, target_batch = make_batch()\n",
        "\n",
        "    for epoch in range(5000):\n",
        "        # make hidden shape [num_layers * num_directions, batch_size, n_hidden]\n",
        "        hidden = torch.zeros(1, batch_size, n_hidden)\n",
        "\n",
        "        optimizer.zero_grad()\n",
        "        # input_batch : [batch_size, max_len(=n_step, time step), n_class]\n",
        "        # output_batch : [batch_size, max_len+1(=n_step, time step) (becase of 'S' or 'E'), n_class]\n",
        "        # target_batch : [batch_size, max_len+1(=n_step, time step)], not one-hot\n",
        "        output = model(input_batch, hidden, output_batch)\n",
        "        # output : [max_len+1, batch_size, n_class]\n",
        "        output = output.transpose(0, 1) # [batch_size, max_len+1(=6), n_class]\n",
        "        loss = 0\n",
        "        for i in range(0, len(target_batch)):\n",
        "            # output[i] : [max_len+1, n_class, target_batch[i] : max_len+1]\n",
        "            loss += criterion(output[i], target_batch[i])\n",
        "        if (epoch + 1) % 1000 == 0:\n",
        "            print('Epoch:', '%04d' % (epoch + 1), 'cost =', '{:.6f}'.format(loss))\n",
        "        loss.backward()\n",
        "        optimizer.step()\n",
        "\n",
        "    # Test\n",
        "    def translate(word, args):\n",
        "        input_batch, output_batch, _ = make_batch([[word, 'P' * len(word)]], args)\n",
        "\n",
        "        # make hidden shape [num_layers * num_directions, batch_size, n_hidden]\n",
        "        hidden = torch.zeros(1, 1, args.n_hidden)\n",
        "        output = model(input_batch, hidden, output_batch)\n",
        "        # output : [max_len+1(=6), batch_size(=1), n_class]\n",
        "\n",
        "        predict = output.data.max(2, keepdim=True)[1] # select n_class dimension\n",
        "        decoded = [char_arr[i] for i in predict]\n",
        "        end = decoded.index('E')\n",
        "        translated = ''.join(decoded[:end])\n",
        "\n",
        "        return translated.replace('P', '')\n",
        "\n",
        "    print('test')\n",
        "    print('man ->', translate('man'))\n",
        "    print('mans ->', translate('mans'))\n",
        "    print('king ->', translate('king'))\n",
        "    print('black ->', translate('black'))\n",
        "    print('upp ->', translate('upp'))"
      ],
      "outputs": [],
      "execution_count": null
    }
  ],
  "metadata": {
    "anaconda-cloud": {},
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.6.1"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 4
}